<?php
/**
 * Description:
 * Author: hezhizheng
 * Date: 2018/11/14
 * Time: 8:48
 * Created by Created by Panxsoft.
 */

namespace App\Services\Repositories;

use App\Http\Controllers\Api\V1\App\ProductController;
use App\Http\Requests\Products\ProductIndexRequest;
use App\Models\Currency;
use App\Models\Customers\Customer;
use App\Models\Extension\Extension;
use App\Models\Layout\LayoutModule;
use App\Models\Manufacturer\Manufacturer;
use App\Models\Manufacturer\ManufacturerDescription;
use App\Models\Manufacturer\ManufacturerDescriptionToStore;
use App\Models\Manufacturer\ManufacturerToStore;
use App\Models\Options\Option;
use App\Models\Options\OptionValueDescription;
use App\Models\Order\OrderProduct;
use App\Models\Products\Product;
use App\Models\Products\ProductAttribute;
use App\Models\Products\ProductOption;
use App\Models\Products\ProductOptionValue;
use App\Models\Products\ProductToStore;
use App\Models\StockStatus;
use App\Models\Store;
use App\Services\Locale\Language;
use App\Services\MobileApp\AppEnv;
use App\Services\Utils\ApiChecker;
use App\Services\Utils\Guard;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Collection as BaseCollection;
use Illuminate\Support\Facades\DB;

class ProductRepository
{
    use ApiChecker;

    const DEFAULT_ORDER_BYS = [
        ['order_by' => 'date_added', 'sort' => 'desc'],
    ];

    const PRODUCT_SEARCH_SELECTS = [
        'product_id', 'model', 'sku', 'quantity',
        'price', 'status', 'image', 'sort_order',
        'viewed', 'minimum', 'stock_status_id', 'date_added',
    ];

    public static $per_page = 10;
    public static $page = 1;
    public static $current_num = 0;

    public function __construct()
    {
        self::$per_page = request()->per_page ?? self::$per_page;
        self::$page = request()->page ?? self::$page;
        self::$current_num = (self::$page-1)*self::$per_page;
    }

    /**
     * @param $product_id
     * @return Product|Product[]|Collection|\Illuminate\Database\Eloquent\Model|null
     * @throws \Exception
     */
    public function findOnShelfProductOrFailed($product_id)
    {
        $product = (new Product)->filterOnShelf()->find($product_id);
            $this->checkItemExist($product);
        return $product;
    }

    /**
     * @param ProductIndexRequest $request
     * @return Collection|LengthAwarePaginator
     * @throws \Exception
     */
    public function getAppProducts(ProductIndexRequest $request)
    {
        $search_type = $request->search_type;

        switch($search_type)
        {
            case Extension::CODE_LATEST: // 最新商品
                $request->sort_column = 'date_added';
                $request->sort_direction = 'DESC';
                $products = $this->getProducts($request);
                break;
            case Extension::CODE_BESTSELLER: // 热销商品
                $products = $this->getBestSellerProducts($request);
                break;
            case Extension::CODE_SPECIAL: // 特价商品
                $products = $this->getSpecialProducts($request);
                break;
            case Extension::CODE_FEATURED: // 推荐商品
                $products = $this->getFeaturedProducts($request);
                break;
            default :
                $products = $this->getProducts($request);
                break;
        }

        return $products;
    }

    /**
     * @param ProductIndexRequest $request
     * @return Collection|LengthAwarePaginator
     * @throws \Exception
     */
    private function getBestSellerProducts(ProductIndexRequest $request)
    {
        /** @var Currency $currency */
        $currency = AppEnv::currency();

        $per_page = $request->per_page ?: 10;
        $order_products = $this->queryBestSellerProducts($per_page, null);

        $this->formatProducts($order_products, $currency);

        return $order_products;
    }

    /**
     * @param null $per_page
     * @param null $limit
     * @return Collection|LengthAwarePaginator
     * @throws \Exception
     */
    public function queryBestSellerProducts($per_page = null, $limit = null)
    {
        /** @var Builder|Collection $order_products */
        $order_products = (new OrderProduct)
            ->has('orderByPaid')
            ->has('productByOnSale')
            ->groupBy('product_id')
            ->with([
                'productByOnSale' => function (BelongsTo $query) {
                    $query->select(self::PRODUCT_SEARCH_SELECTS)
                        ->with([
                            'productDescription',
                            'productDiscounts',
                            'productSpecials'
                            ]);
                }
            ])
            ->select('product_id', DB::raw('SUM(quantity) as selling_quantity'))
            ->orderBy('selling_quantity', 'DESC')
            ->latest('product_id')
            ->when($limit, function(Builder $query, $limit){
                return $query->limit($limit)->get();
            })
            ->when($per_page, function(Builder $query){
                return $query;
            });

        if ($per_page) {
            /** @var LengthAwarePaginator|Collection $order_product */
            $order_products = $this->getPaginate($order_products, ProductController::IS_BEST_SELLER);
        }

        $order_products->transform(function(OrderProduct $order_product){
            $order_product->productByOnSale->selling_quantity = $order_product->selling_quantity;

            return $order_product->productByOnSale;
        });

        return $order_products;
    }

    /**
     * @param ProductIndexRequest $request
     * @return mixed
     * @throws \Exception
     */
    private function getSpecialProducts(ProductIndexRequest $request)
    {
        $currency = AppEnv::currency();

        /** @var LengthAwarePaginator $specials_products */
        $specials_products = $this
            ->querySpecialProducts()
            ->paginate($request->per_page ?: 10);

        $this->formatProducts($specials_products, $currency);

        return $specials_products;
    }

    /**
     * @param ProductIndexRequest $request
     * @return mixed
     * @throws \Exception
     */
    private function getFeaturedProducts(ProductIndexRequest $request)
    {
        $currency = AppEnv::currency();
        $module_product_ids = $request->module()->setting['product'] ?? [];

        /** @var LengthAwarePaginator $specials_products */
        $specials_products = $this
            ->queryFeaturedProducts($module_product_ids)
            ->paginate($request->per_page ?: 10);

        $this->formatProducts($specials_products, $currency);

        return $specials_products;
    }

    /**
     * @return Product|Builder
     */
    public function querySpecialProducts()
    {
        $specials_query = (new Product)
            ->filterOnShelf()
            ->has('productSpecials')
            ->with(
                'productDescription',
                'productDiscounts',
                'productSpecials'
            )
            ->select(self::PRODUCT_SEARCH_SELECTS);

        return $specials_query;
    }

    /**
     * 推荐商品
     *
     * @param array $product_ids
     *
     * @return Product|Builder
     */
    public function queryFeaturedProducts(array $product_ids)
    {
        $products = (new Product)
            ->filterOnShelf()
            ->whereKey($product_ids)
            ->with(
                'productDescription',
                'productDiscounts',
                'productSpecials'
            )
            ->select(self::PRODUCT_SEARCH_SELECTS);

        return $products;
    }

    /**
     * 根据id列表查询产品
     * @param array $product_ids
     * @return Product|Builder
     */
    public function getProductsByIdArray(array $product_ids)
    {
        $currency = AppEnv::currency();
//        $products = (new Product)->filterOnShelf()->whereIn('product_id', $product_ids)->get();
        $products_query = (new Product)
                ->filterProductsIdArray($product_ids)
                ->filterOnShelf()
                ->with(['productDescription', 'stockStatus', 'productDiscounts', 'productSpecials']);
        $products = $this->getPaginate($products_query);
        $this->formatProducts($products, $currency);
        return $products;
    }

    /**
     * @param ProductIndexRequest $request
     *
     * @return Collection|LengthAwarePaginator
     * @throws \Exception
     */
    public function getProducts(ProductIndexRequest $request)
    {
        $currency = AppEnv::currency();

        $price_range = $this->formatPriceRange($request->price_range, $currency);

        $product_query = (new Product)
            ->filterOnShelf()
            ->with(['productDescription', 'stockStatus', 'productDiscounts', 'productSpecials'])
            ->select(self::PRODUCT_SEARCH_SELECTS)
            ->filterCategoryId($request->category_id)
            ->filterStockStatus($request->stock_status_id)
            ->filterManufacturers($request->manufacturerIds())
            ->filterOptionValues($request->optionValueIds())
            ->filterAttributes($request->get('attributes'))
//            ->filterKeyword($request->keyword)
            ->searchKeyword($request->keyword)
            ->filterPriceRange($price_range)
            ->filterOrderBy($request->sort_column)
            ->orderBy($request->sort_column ?? 'date_added', $request->sort_direction ?? 'DESC');

        $products = $this->getPaginate($product_query);

        $this->formatProducts($products, $currency);

        return $products;
    }

    /**
     *
     * @param Request $request
     * @return LengthAwarePaginator
     */
    public function getCustomerWishProducts(Request $request)
    {
        $currency = Currency::fromEnv();

        /** @var Customer $customer */
        $customer = Guard::customer();

        /** @var LengthAwarePaginator $products */
        $products = (new Product)
            ->select([
                'product.product_id AS product_id',
                'model', 'sku', 'quantity',
                'price', 'status', 'image', 'sort_order',
                'viewed', 'minimum', 'stock_status_id',
                'product.date_added AS product_date_added',
            ])
            ->join('customer_wishlist', 'product.product_id', 'customer_wishlist.product_id')
            ->where('customer_wishlist.customer_id', $customer->customer_id)
            ->with(['productDescription', 'stockStatus', 'productDiscounts', 'productSpecials'])
            ->filterOnShelf()
            ->orderBy('customer_wishlist.date_added', 'DESC')
            ->paginate($request->per_page ?: 15);

        $this->formatProducts($products, $currency);

        return $products;
    }

    /**
     * @param LengthAwarePaginator $products
     * @param Currency $currency
     */
    public function formatProducts(LengthAwarePaginator $products, Currency $currency): void
    {
        /** @var LengthAwarePaginator|Collection $products */
        $products->map(function (Product $product) use ($currency) {

            $special_price = $product->productSpecials->first()->price ?? null;

            $product->name = $product->productDescription->name;
            $product->currency_settle_price = $currency->exchangePrice($product->settle_price); // 这里的商品price不能被处理
            $product->price = $currency->exchangePrice($product->price);
            $product->special_price = $special_price ? $currency->exchangePrice($special_price) : null;
            $product->symbol_left = $currency->symbol_left;
            $product->symbol_right = $currency->symbol_right;

            $product->addHidden(['productDescription', 'stockStatus', 'productDiscounts', 'productSpecials']);
        });

        $products->makeHidden(['stock_status_id', 'date_added']);

    }

    /**
     * @param array|null $product_ids
     *
     * @param int $store_id
     *
     * @return Manufacturer[]|Collection
     */
    public function getFilterManufacturers(?array $product_ids, int $store_id): Collection
    {
        // 原来的处理逻辑
//        $manufacturer_ids = (new ManufacturerToStore)
//            ->where('store_id', $store_id)
//            ->pluck('manufacturer_id');
//
//        $manufacturers = (new Manufacturer)
//            ->filterProducts($product_ids)
//            ->whereKey($manufacturer_ids)
//            ->get();

//        return $manufacturers;

        $language_id = AppEnv::languageId();

        $manufacturers = (new ManufacturerDescription)
            ->filterManufacturerDescriptionToStore($store_id)
            ->filterProducts($product_ids)
            ->where('language_id', $language_id)
            ->oldest('sort_order')
            ->get();

        return $manufacturers;
    }

    /**
     * @param array|null $product_ids
     *
     * @return Manufacturer[]|Collection
     */
    public function getFilterStockStatus(?array $product_ids): Collection
    {
        $status = (new StockStatus)
            ->filterProducts($product_ids)
            ->where('language_id', AppEnv::languageId())
            ->get();

        return $status;
    }

    /**
     * @param array $product_ids
     * @return Option[]|\Illuminate\Database\Eloquent\Builder[]|Collection|mixed
     */
    public function getFilterOptions(array $product_ids)
    {
        $option_ids = (new ProductOption)
            ->whereIn('product_id', $product_ids)
            ->pluck('option_id')
            ->unique()
            ->values();

        $options = (new Option)
            ->whereKey($option_ids)
            ->has('optionValueDescriptions')
            ->with(['productOptionValues' => function($query) use($product_ids){  // todo 排除商品没有关联的option_value_id,加了速度很慢
                return $query->whereIn('product_id', $product_ids);
            }])
            ->with(['optionDescription', 'optionValueDescriptions'])
            ->get();

        $options = $this->formatOptions($options);

        return $options;
    }

    /**
     * @param $options
     * @return mixed
     */
    private function formatOptions(Collection $options): BaseCollection
    {
        return $options->map(function (Option $option) {

            // todo 还没有处理product_option 只有value值的类型。
            $option_value_ids = $option->productOptionValues->pluck('option_value_id')->unique()->all() ?? [];
            $option_values = $option->optionValueDescriptions->whereIn('option_value_id', $option_value_ids)->values();

            return [
                'option_id' => $option->option_id,
                'name'      => $option->optionDescription->name,
                'values'    => $option_values
                    ->map(function (OptionValueDescription $optionValueDescriptions) {
                        return [
                            'option_value_id'   => $optionValueDescriptions->option_value_id,
                            'option_value_name' => $optionValueDescriptions->name,
                        ];
                    }),
            ];
        });
    }

    /**
     * @param $product_ids
     *
     * @return BaseCollection
     */
    public function getFilterAttributes(array $product_ids): BaseCollection
    {
        $product_attributes = (new ProductAttribute)
            ->whereIn('product_id', $product_ids)
            ->with('attributeDescription')
            ->where('language_id', AppEnv::languageId())
            ->get();

        return $product_attributes
            ->groupBy('attribute_id')
            ->map(function (Collection $product_attributes, int $attribute_id) {
                /** @var ProductAttribute $prod_attr */
                $prod_attr = $product_attributes->first();

                return [
                    'attribute_id' => $attribute_id,
                    'name'         => $prod_attr->attributeDescription->name,
                    'values'       => $product_attributes->pluck('text')->unique()->values(),
                ];
            })
            ->values();
    }

    /**
     * 切换货币之后的价格筛选
     * @param $price_range
     * @param Currency $currency
     * @return array|null
     */
    private function formatPriceRange(?array $price_range, Currency $currency)
    {
        if (is_null($price_range)) return null;
        $format_price_range = array_map(function ($exchange_price) use ($currency) {
            // exchange price convert to default currency price
            return Currency::transformStoreSettingCurrencyPrice($exchange_price, $currency);
        }, $price_range);

        return $format_price_range;
    }

    /**
     * @param Builder $product_query
     * @param null $is_best_seller
     * @return LengthAwarePaginator
     */
    private function getPaginate(Builder $product_query, $is_best_seller = null)
    {
        // 搜索商品总数，必须放在offset,limit之前。
        if (request()->has('price_range') || $is_best_seller) {
            $total = $product_query->get()->count();
        } else {
            $total = $product_query->count();
        }

        /** @var Collection $products */
        $products = $product_query
            ->offset(self::$current_num)
            ->limit(self::$per_page)
            ->get();

        //自定义分页类
        $result = new LengthAwarePaginator($products, $total, self::$per_page, self::$page, [
            'path' => Paginator::resolveCurrentPath(),
            'pageName' => 'page',
        ]);

        return $result;
    }
}

